﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using Object = UnityEngine.Object;

public static class UnityUtils
{
    public static Vector3 HidePos = new Vector3(9999, 9999, 9999);
    public static Vector3 ShowPos = new Vector3(0, 0, 0);

    /// <summary>
    /// 两个向量的球形插值
    /// </summary>
    /// <param name="a">向量a</param>
    /// <param name="b">向量b</param>
    /// <param name="t">t的值在[0..1]</param>
    /// <returns></returns>
    public static Vector3 Slerp(Vector3 a, Vector3 b, float t)
    {
        if (t <= 0)
        {
            return a;
        }
        else if (t >= 1)
        {
            return b;
        }

        var angle = Vector3.SignedAngle(a, b, Vector3.up);
        angle = WrapAngle360(angle);
        //GLog.Error(angle.ToString() + " " + WrapAngle360(angle).ToString());

        bool inverse = angle > 180 && angle < 270;
        angle = Vector3.Angle(a, b);
        angle = inverse ? 360 - angle : angle;

        Vector3 v = RotateTo(a, b, angle * t, inverse);

        //向量的长度，跟线性插值一样计算
        float length = b.magnitude * t + a.magnitude * (1 - t);
        return v.normalized * length;
    }

    /// <summary>
    /// 将向量from向向量to旋转角度angle
    /// </summary>
    /// <param name="from"></param>
    /// <param name="to"></param>
    /// <param name="angle"></param>
    /// <returns></returns>
    public static Vector3 RotateTo(Vector3 from, Vector3 to, float angle, bool inverse)
    {
        //如果两向量角度为0
        if (Vector3.Angle(from, to) == 0)
        {
            return from;
        }

        Vector3 from1 = inverse ? to : from;
        Vector3 to1 = inverse ? from : to;

        //旋转轴
        Vector3 n = Vector3.Cross(from1, to1);

        //旋转轴规范化
        n.Normalize();

        //旋转矩阵
        Matrix4x4 rotateMatrix = new Matrix4x4();

        //旋转的弧度
        double radian = angle * Math.PI / 180;
        float cosAngle = (float)Math.Cos(radian);
        float sinAngle = (float)Math.Sin(radian);

        //矩阵的数据
        //这里看不懂的自行科普矩阵知识
        rotateMatrix.SetRow(0,
            new Vector4(n.x * n.x * (1 - cosAngle) + cosAngle, n.x * n.y * (1 - cosAngle) + n.z * sinAngle,
                n.x * n.z * (1 - cosAngle) - n.y * sinAngle, 0));
        rotateMatrix.SetRow(1,
            new Vector4(n.x * n.y * (1 - cosAngle) - n.z * sinAngle, n.y * n.y * (1 - cosAngle) + cosAngle,
                n.y * n.z * (1 - cosAngle) + n.x * sinAngle, 0));
        rotateMatrix.SetRow(2,
            new Vector4(n.x * n.z * (1 - cosAngle) + n.y * sinAngle, n.y * n.z * (1 - cosAngle) - n.x * sinAngle,
                n.z * n.z * (1 - cosAngle) + cosAngle, 0));
        rotateMatrix.SetRow(3, new Vector4(0, 0, 0, 1));

        Vector4 v = new Vector4(from.x, from.y, from.z, 0);
        Vector3 vector = new Vector3();
        for (int i = 0; i < 3; ++i)
        {
            for (int j = 0; j < 3; j++)
            {
                vector[i] += v[j] * rotateMatrix[j, i];
            }
        }

        return vector;
    }
    
    /// <summary>Is the vector within 0.01 length?</summary>
    /// <param name="v"></param>
    /// <returns>True if the square magnitude of the vector is within 0.01</returns>
    public static bool AlmostZero(this Vector2 v)
    {
        return v.sqrMagnitude < 0.01;
    }

    public static float Distance2D(Vector3 from, Vector3 to)
    {
        from.y = 0;
        to.y = 0;
        return Vector3.Distance(from, to);
    }

    public static float Distance2DSqr(Vector3 from, Vector3 to)
    {
        from.y = 0;
        to.y = 0;
        return (from - to).sqrMagnitude;
    }

    public static Vector3 TransformPoint(Vector3 position, float rotDegree, Vector3 pos)
    {
        Quaternion rotateYaw = Quaternion.AngleAxis(rotDegree, Vector3.up);
        var matrix4X4 = Matrix4x4.TRS(Vector3.zero, rotateYaw, Vector3.one);
        return matrix4X4.MultiplyPoint(pos);
    }

    public static Vector3 TransformDirection(Vector3 direction, float rotDegree)
    {
        var rotateYaw = Quaternion.AngleAxis(rotDegree, Vector3.up);
        var matrix4X4 = Matrix4x4.TRS(Vector3.zero, rotateYaw, Vector3.one);
        return  matrix4X4.MultiplyVector(direction);
    }
    
    
    public static void SetActiveDelegate(GameObject obj)
    {
        if (obj)
            obj.SetActive(true);
    }

    public static void SetDeActiveDelegate(GameObject obj)
    {
        if (obj)
            obj.SetActive(false);
    }

    public static void SetDark(this Image image, bool isDark = true)
    {
        if (!image) return;
        image.color = isDark ? Color.gray : Color.white;
    }
    public static GameObject InstantiateAndReset(GameObject original, Transform parent)
    {
        var ret = GameObject.Instantiate(original, parent);
        ret.transform.Reset();
        return ret;
    }

    public static void Reset(this GameObject t, bool isLocal = true, bool isActive = true, Transform parent = null)
    {
        if (!t)
            return;
        Reset(t.transform, isLocal, isActive, parent);
    }

    public static void Reset(this Transform t, bool isLocal = true, bool isActive = true, Transform parent = null)
    {
        if (!t)
            return;
        if (parent)
            t.SetParent(parent);
        if (isLocal)
        {
            t.localPosition = Vector3.zero;
            t.localRotation = Quaternion.identity;
            t.localScale = Vector3.one;
        }
        else
        {
            t.position = Vector3.zero;
            t.rotation = Quaternion.identity;
            t.localScale = Vector3.one;
        }

        t.gameObject.SetActive(isActive);
    }
    
    public static void EnableEx(this Renderer r, bool isEnable)
    {
        if (r)
            r.enabled = isEnable;
    }

    public static void SetActiveEx(this Transform go, bool isActive)
    {
        if (go == null)
        {
            return;
        }
        go.gameObject.SetActiveEx(isActive);
    }

    public static T GetComponentInParent<T>(this GameObject go, bool includeInactive)
    {
        if (!includeInactive)
        {
            return go.GetComponentInParent<T>();
        }

        Transform trans = go.transform;
        while (trans != null)
        {
            if (trans.GetComponent<T>() != null)
            {
                break;
            }

            trans = trans.parent;
        }

        return trans.GetComponent<T>();
    }
    
    public static void SetActiveEx(this GameObject go, bool isActive)
    {
        if (go == null)
        {
            GLog.Warning("GameObject go is null~!");
            return;
        }

        if (go.activeSelf != isActive)
        {
            go.SetActive(isActive);
        }
    }

    public static void SetActiveEx(this GameObject go, bool isActive, bool isSimple)
    {
        if (go == null)
        {
            GLog.Warning("GameObject go is null~!");
            return;
        }

        if (isSimple)
        {
            if (isActive)
            {
                go.transform.localPosition = ShowPos;
            }
            else
            {
                go.transform.localPosition = HidePos;
            }
            return;
        }

        if (go.activeSelf != isActive)
        {
            go.SetActive(isActive);
        }
    }
    
    public static void SetActiveByPos(this Transform trans, bool bActive, Vector3 pos = default)
    {
        try
        {
            if (bActive)
            {
                trans.localPosition = pos;
            }
            else
            {
                trans.localPosition = new Vector3(99999, 99999, 0);
            }
        }
        catch (Exception)
        {
            GLog.Warning("Transform is null~!");
        }
    }
    
    public static void SetActiveByPos(this GameObject go, bool bActive, Vector3 pos = default)
    {
        go.transform.SetActiveByPos(bActive,pos);
    }
    
    public static void Destroy<T>(T t) where T : Object
    {
        if (Application.isPlaying)
        {
            Object.Destroy(t);
        }
        else
        {
            GameObject.DestroyImmediate(t);
        }
    }
    
    public static Component RequireComponent(this GameObject go, Type ComponentType)
    {
        if (go)
        {
            var ret = go.GetComponent(ComponentType);
            if (ret)
            {
                return ret;
            } //if
            else
            {
                return go.AddComponent(ComponentType);
            }
        }

        return null;
    }

    public static Component RequireComponent(this Transform go, Type ComponentType)
    {
        if (go)
        {
            var ret = go.GetComponent(ComponentType);
            if (ret)
            {
                return ret;
            } //if
            else
            {
                return go.gameObject.AddComponent(ComponentType);
            }
        }

        return null;
    }


    public static T GetOrAddComponent<T>(this GameObject go) where T : Component
    {
        var animancer = go.GetComponent<T>();
        if (animancer != null)
        {
            return animancer;
        }
        else
        {
            return go.AddComponent<T>();
        }
    }
    public static T RequireComponent<T>(this GameObject go) where T : Component
    {
        if (go)
        {
            var ret = go.GetComponent<T>();
            if (ret)
            {
                return ret;
            }
            else
            {
                return go.AddComponent<T>();
            }
        }
        return null;
    }
    
    public static T RequireComponent<T>(this Transform go) where T : Component
    {
        if (go)
        {
            var ret = go.GetComponent<T>();
            if (ret)
            {
                return ret;
            } //if
            else
            {
                return go.gameObject.AddComponent<T>();
            }
        }

        return null;
    }

    static Stack<Transform> searchStack = new Stack<Transform>();

    public static Transform SearchHierarchy(Transform current, string name, bool ignoreCase = false)
    {
        try
        {
            searchStack.Push(current);
            StringComparison cmp =
                ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
            while (searchStack.Count > 0)
            {
                var cur = searchStack.Pop();
                if (!cur)
                    continue;
                if (cur.name.Equals(name, cmp))
                    return cur;
                for (int i = 0; i < cur.childCount; ++i)
                    searchStack.Push(cur.GetChild(i));
            }
        }
        finally
        {
            searchStack.Clear();
        }

        return null;
    }
    
    public static void SetLayer(GameObject obj, int layer, bool recursive = false)
    {
        if (obj)
        {
            SetLayer(obj.transform, layer, recursive);
        }
    }

    public static void SetLayer(Transform trans, int layer, bool recursive = false)
    {
        if (trans)
        {
            trans.gameObject.layer = layer;
            if (recursive)
            {
                for (int i = 0; i < trans.childCount; ++i)
                {
                    SetLayer(trans.GetChild(i), layer, true);
                }
            }
        }
    }

    // public static Vector3 V2dToSceneVec3(this Vector2 vec2d, float startHeight = 10000)
    // {
    //     var pos = new Vector3(vec2d.x, startHeight, vec2d.y);
    //     return SceneUtils.GetTerrainPosition(pos);
    // }
    
    public static Vector2 Vec3ToV2d(this Vector3 vec)
    {
        return new Vector2(vec.x, vec.z);
    }

    public static Vector3 V2dToVec3(this Vector2 vec2d, float height = 0)
    {
        return new Vector3(vec2d.x, height, vec2d.y);
    }

    public static Vector3 ToDirection(this Vector3 vec)
    {
        vec.y = 0;
        return vec;
    }

    public static GameObject AddRootObject(string name)
    {
        var ret = new GameObject(name);
        ret.transform.Reset();
        GameObject.DontDestroyOnLoad(ret);
        GameObject main = GameObject.Find("GameMain");
        ret.transform.SetParent(main != null ? main.transform : null);
        return ret;
    }
    
    /// <summary>
    /// server uses right hand coordinate system, 0 rotation degree faces x positive, 90 degree faces z positive
    /// 仅用于y为0的单位向量，比如主角的朝向。。。
    /// </summary>
    /// <param name="curDir"></param>
    /// <returns></returns>
    public static float GetRotationFromDir(Vector3 curDir)
    {
        float v = Vector3.Dot(curDir, Vector3.forward);
        float yaw = Mathf.Acos(v);
        //[-PI, 0]
        yaw = curDir.x < 0 ? -yaw : yaw;
        // yaw: [-PI, PI] => [0, 2PI]
        yaw = MathLib.NormalizeRadian(yaw);
        //float yaw = Mathf.Atan2(curDir.x, curDir.z);
        return yaw;
    }

    public static float GetAngleFromDirection(Vector3 dir)
    {
        float axisY = GetRotationFromDir(dir) * Mathf.Rad2Deg;
        axisY = WrapAngle(axisY);
        axisY = axisY % 360;
        return axisY;
    }

    /** 后端算法 返回点1对于点0的角度 */
    public static float RotationBetweenPos(float x0,float y0,float x1,float y1)
    {
        var value = Mathf.Atan2(x1 - x0, y1 - y0);
        value = Mathf.Rad2Deg * value;
        return WrapAngle360(value);
    }
    
    public static float WrapAngle(float angle)
    {
        angle = angle % 360;
        if (angle > 180f) angle -= 360f;
        if (angle < -180f) angle += 360f;
        return angle;
    }

    public static float WrapAngle360(float angle)
    {
        angle = angle % 360;
        if (angle < 0)
        {
            angle += 360;
        }

        return angle;
    }
    
    public static float HorizontalFov(this Camera camera)
    {
        var tan = Mathf.Tan(camera.fieldOfView / 2 * Mathf.Deg2Rad);
        return 2 * Mathf.Atan(camera.aspect * tan) * Mathf.Rad2Deg;
    }

    public static float LerpAngleUnClamped(float a, float b, float t)
    {
        float num = Mathf.Repeat(b - a, 360f);
        if ((double) num > 180.0)
            num -= 360f;
        return a + num * t;
    }

    public static Color GetColor(string colorStr)
    {
        Color ret;
        ColorUtility.TryParseHtmlString("#" + colorStr, out ret);
        return ret;
    }

    public static LinkedListNode<T> Find<T>(this LinkedList<T> lst, Predicate<T> match)
    {
        if (lst == null || lst.Count <= 0) return null;
        var node = lst.First;
        while (node != null)
        {
            if (match(node.Value))
                return node;
            else
                node = node.Next;
        }

        return null;
    }
    
    public static void CallAction(Action action)
    {
        if (action != null) action();
    }

    public static void CallAction<T>(Action<T> action, T t)
    {
        if (action != null) action(t);
    }

    public static void CallAction<T, T1>(Action<T, T1> action, T t, T1 t1)
    {
        if (action != null) action(t, t1);
    }

    public static void CallAction<T, T1, T2>(Action<T, T1, T2> action, T t, T1 t1, T2 t2)
    {
        if (action != null) action(t, t1, t2);
    }

    public static void Switch<T>(ref T t1, ref T t2)
    {
        T temp = t1;
        t1 = t2;
        t2 = temp;
    }

    public static void SetParent(Transform child, Transform parent,bool changeLayer = true)
    {
        if (child.parent != parent)
            child.SetParent(parent);
        child.localPosition = Vector3.zero;
        child.localRotation = Quaternion.Euler(0, 0, 0);
        child.localScale = Vector3.one;
        if (child.gameObject.layer != parent.gameObject.layer && changeLayer)
            SetLayer(child.gameObject, parent.gameObject.layer, true);
    }
    
    public static void Clear(this StringBuilder stringBuilder)
    {
        stringBuilder.Length = 0;
    }
    
    public static Vector3 ToXZVerticalVector(this Vector3 source)
    {
        if (Math.Abs(source.x) < 0.001f)
        {
            return new Vector3(1, 0, 0);
        }

        return new Vector3(-source.z / source.x, 0, 1).normalized;
    }

    public static void Vector3ToArray(Vector3 vector, ref float[] array)
    {
        array[0] = vector.x;
        array[1] = vector.y;
        array[2] = vector.z;
    }

    public static Vector3 RecastToUnityPosition(this Vector3 recastPos)
    {
        recastPos.x = -recastPos.x;
        return recastPos;
    }

    public static Vector3 Float3ArrayToVector3(float[] array)
    {
        return new Vector3(array[0], array[1], array[2]);
    }
    
    public static List<T> MarshalPtrToStructArray<T>(IntPtr p, int count)
    {
        var list = new List<T>();
        var size = Marshal.SizeOf(typeof(T));
        for (int i = 0; i < count; i++)
        {
            var intPtr = p + size * i;
            try
            {
                T t = (T)Marshal.PtrToStructure(intPtr, typeof(T));;
                list.Add(t);
            }
            catch (Exception e)
            {
                GLog.Error(e.ToString());
            }
        }
        return list;
    }

    public static Color TurnColor(int r, int g, int b, int a)
    {
        return new Color(r / 255f, g / 255f, b / 255f, a / 255f);
    }
    
    /// <summary>
    /// Returns true if the object is either a null reference or has been destroyed by unity.
    /// This will respect ISPDisposable over all else.
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public static bool IsNullOrDestroyed(this System.Object obj)
    {
        if (object.ReferenceEquals(obj, null)) return true;

        if (obj is UnityEngine.Object) return (obj as UnityEngine.Object) == null;

        return false;
    }
}